#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

using namespace std;


//数组大小
const int BASESIZE = 1024;
const int HOSTSIZE = 256;
const int PWDSIZE = 256;
const int PWDENVSIZE = 1024;
const int GARGVSIZE = 64;
const int GENVSIZE = 128;
//命令行参数
char *gargv[GARGVSIZE];
int gargc;

//路径
char pwd[PWDSIZE];
char pwdenv[PWDSIZE];

// 我的系统的环境变量
char *genv[GENVSIZE];

// 全局变量与重定向有关
#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppRedir    3
int redir = NoneRedir;
char *filename = nullptr;

//退出码
int lastcode = 0;
//获取用户名
string GetUserName()
{
	//从环境变量中获取用户名
	string username = getenv("USER");
	return username.empty() ? "?" : username;
}

//获取主机名
string GetHostName()
{
	char hostname[BASESIZE];
	int result = gethostname(hostname, sizeof(hostname));
    return  strlen(hostname) == 0 ? "?" : hostname;
}


//获取路径
string GetPwd()
{
	//获取路径
	getcwd(pwd,sizeof(pwd));
	//复制到环境变量中
	sprintf(pwdenv,"PWD=");
	for(int i = 0; pwd[i]; i++) pwdenv[i+4] = pwd[i];
	putenv(pwdenv);
	return pwd;
}

//将路径简化
string LastDir()
{
	string s = GetPwd();
	if(s == "/"|| s == "?")	return s;
	int pos = s.rfind("/");
	if(pos == string::npos) return "?";
	
	return s.substr(pos+1);
}

// 1.输出命令行提示符
void PrintCommandLine()
{
	//xzy@hcss-ecs-2a19:~/112$ 
	printf("%s@%s:[%s]>",GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());
	fflush(stdout);//刷新语言级缓冲区
}

bool GetCommandLine(char command_buffer[], int size)   // 2. 获取用户命令
{
	//获取命令行
	char *result = fgets(command_buffer, size, stdin);
	if(!result)
    {
        return false;
    }
	command_buffer[strlen(command_buffer)-1] = 0;
    if(strlen(command_buffer) == 0) return false;
    return true;
}

//初始化命令行参数
void ResetCommandline()
{
	memset(gargv, 0, sizeof(gargv));
    gargc = 0;
	
	redir = NoneRedir;
	filename = nullptr;
}


//去掉文件前缀空格
void rimSpace()
{
	while((*filename) == ' ')
		filename++;
}

//判断重定向
void ParseRedir(char command_buffer[], int len)
{
	int end = len-1;
	while(end >= 0)
	{
		if(command_buffer[end] == '<')
		{
			redir = InputRedir;
			command_buffer[end] = 0;
			filename = &command_buffer[end+1];
			rimSpace();
			break;
		}
		else if(command_buffer[end] == '>')
		{
			if(end - 1 >= 0 && command_buffer[end-1] == '>')
			{
				
				redir = AppRedir;
				command_buffer[end-1] = 0;
				filename = &command_buffer[end+1];
				rimSpace();
				break;
			}
			else
			{
				redir = OutputRedir;
				command_buffer[end] = 0;
				filename = &command_buffer[end+1];
				rimSpace();
				break;
			}
		}
		else
		{
			end--;
		}
	}

}

//解析命令
void ParseCommand(char command_buffer[])
{
	const char *s = " ";
	gargv[gargc++] = strtok(command_buffer,s);
	while((bool)(gargv[gargc++] = strtok(nullptr, s)));
	gargc--;
}

void debug()
{
    printf("argc: %d\n", gargc);
    for(int i = 0; gargv[i]; i++)
    {
        printf("argv[%d]: %s\n", i, gargv[i]);
    }
}


void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{
    ResetCommandline();
    ParseRedir(command_buffer, len);
    ParseCommand(command_buffer);
}


//判断是否重定向
void DoRedir()
{
    // 1. 重定向应该让子进程自己做！
    // 2. 程序替换会不会影响重定向?不会
    // 0. 先判断 && 重定向
    if(redir == InputRedir)
    {
        if(filename)
        {
            int fd = open(filename, O_RDONLY);
            if(fd < 0)
            {
                exit(2);
            }
            dup2(fd, 0);
        }
        else
        {
            exit(1);
        }
    }
    else if(redir == OutputRedir)
    {
        if(filename)
        {
            int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if(fd < 0)
            {
                exit(4);
            }
            dup2(fd, 1);
        }
        else
        {
            exit(3);
        }
    }
    else if(redir == AppRedir)
    {
        if(filename)
        {
            int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
            if(fd < 0)
            {
                exit(6);
            }
            dup2(fd, 1);
        }
        else
        {
            exit(5);
        }
    }
    else
    {
        // 没有重定向,Do Nothong!
    }
}



// 4. 执行命令
bool ExecuteCommand()
{
	//创建子进程
	pid_t pid = fork();

	if(pid == 0)
	{
		//重定向
		DoRedir();
		//程序替换
		execvp(gargv[0],gargv);
		exit(100);
	}

	//进程等待
	int status = 0;
	int rid = waitpid(pid,&status,0);
	if(rid > 0)
	{
		if(WIFEXITED(status))
			lastcode = WEXITSTATUS(status);
		else
			lastcode = 100;
	}

	return false;

}

//添加环境变量
void AddEnv(const char *item)
{
	int index = 0;
	while(genv[index] != NULL)
		index++;
	genv[index] = new char[strlen(item)+1];
	strncpy(genv[index],item,strlen(item)+1);

	genv[++index] = NULL;
}


//内建命令
bool CheckAndExecBuiltCommand()
{
	if(strcmp(gargv[0],"env") == 0)
	{
		if(gargc == 1)
		{
			int i = 0;
			while(genv[i] != NULL)
			{
				printf("%s\n",genv[i]);
				i++;
			}
			lastcode = 0;
		}
		else
		{
			lastcode = 1;
		}
		
		return true;
	}
	else if(strcmp(gargv[0],"export") == 0)
	{
		if(gargc == 2)
		{
			AddEnv(gargv[1]);
			lastcode = 0;
		}
		else
		{
			lastcode = 1;
		}

		return true;
	}
	else if(strcmp(gargv[0],"cd") == 0)
	{
		if(gargc == 2)
		{
			chdir(gargv[1]);
			lastcode = 0;
			char cwd[1024];
		    if (getcwd(cwd, sizeof(cwd)) != NULL)
			printf("Current working directory: %s\n", cwd);
		}
		else 
		{
			lastcode = 1;
		}

		return true;
	}
	else if(strcmp(gargv[0],"echo") == 0)
	{
		if(gargc == 2)
		{
			printf("%s\n",gargv[1]);
			lastcode = 0;
		}
		else
		{
			lastcode = 1;
		}

		return true;
	}

	return false;
}
//环境变量表
void InitEnv()
{
    extern char **environ;
    int index = 0;

	while(environ[index] != NULL)
	{
		genv[index] = new char[strlen(environ[index])+1];
		strncpy(genv[index],environ[index],strlen(environ[index])+1);
		index++;
	}

	genv[index] = NULL;
}

int main()
{
	InitEnv();
	char command_buffer[BASESIZE];
	while(1)
	{
		PrintCommandLine(); // 1.输出命令行提示符
		
		if(!GetCommandLine(command_buffer,BASESIZE))//获取命令
			continue;
        //分析命令
		ParseCommandLine(command_buffer,strlen(command_buffer));
		if ( CheckAndExecBuiltCommand() )
        {
            continue;
        }
		//执行命令x
		ExecuteCommand();
		
	}
	return 0;
}
